﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Web.Mvc;
using System.Web.Routing;
using ActivateYourGlutes.Controllers;
using ActivateYourGlutes.Infrastructure;
using ActivateYourGlutes.Model;
using ActivateYourGlutes.Model.Contracts;
using ActivateYourGlutes.Model.Providers;
using ActivateYourGlutes.Payloads.Body;
using ActivateYourGlutes.Tests.Unit.Mocks;
using ActivateYourGlutes.Helpers;
using NUnit.Framework;
using Rhino.Mocks;

namespace ActivateYourGlutes.Tests.Unit.Controllers
{
    [TestFixture]
    public class Test_BodyController : ControllerTestBase
    {
        private IBodyProvider _bodyProvider;
        private IMeasurementTypeProvider _measurementTypeProvider;
        private BodyController _bodyController;
        private Series _weightSeries;
        private Series _bodyfatSeries;
        private Series _chestSeries;
        private MockHttpContext _httpContext;
        private List<MeasurementType> _defaultViewTypes;

        [SetUp]
        public override void Init()
        {
            base.Init();
            _defaultViewTypes = new List<MeasurementType>() {_weight, _bodyFat};
            _weightSeries = new Series {Title = "Weight"};
            _weightSeries.Values.Add(new SeriesValuePair { XDate = new DateTime(2009, 10, 20), Y = 85});
            _bodyfatSeries = new Series { Title = "Body Fat %" };
            _bodyfatSeries.Values.Add(new SeriesValuePair { XDate = new DateTime(2009, 10, 20), Y = 15 });
            _chestSeries = new Series { Title = "Chest" };
            _chestSeries.Values.Add(new SeriesValuePair { XDate = new DateTime(2009, 10, 20), Y = 44 });
            _measurementTypeProvider = MockRepository.GenerateMock<IMeasurementTypeProvider>();
            _measurementTypeProvider.Stub(bp => bp.MeasurementTypes(null)).IgnoreArguments().Return(_measurementTypes);
            _bodyProvider = MockRepository.GenerateMock<IBodyProvider>();
            _bodyController = new BodyController(_bodyProvider, _measurementTypeProvider);
            _httpContext = new MockHttpContext();
            _bodyController.ControllerContext = new ControllerContext(_httpContext, new RouteData(), _bodyController);
            _bodyController.LocaleSettings = _localeSettings;
        }

        #region Index

        [Test]
        public void Test_Index_Get_ReturnsView()
        {
            // Arrange
            _bodyProvider.Expect(bp => bp.DefaultMeasurementTypes).Return(_defaultViewTypes);
            _bodyProvider.Expect(bp => bp.SeriesForIndicator(Arg<Account>.Is.Anything, Arg<MeasurementType>.Is.Equal(_weight), Arg<bool>.Is.Equal(false))).Return(_weightSeries);
            _bodyProvider.Expect(bp => bp.SeriesForIndicator(Arg<Account>.Is.Anything, Arg<MeasurementType>.Is.Equal(_bodyFat), Arg<bool>.Is.Equal(false))).Return(_bodyfatSeries);

            // Act
            ViewResult result = (ViewResult) _bodyController.Index();

            // Assert
            var payload = (IndexPayload) result.ViewData.Model;
            
            Assert.That(payload.HorizontalMeasurementTypesPayload.IncludeButton, Is.True);
            Assert.That(payload.HorizontalMeasurementTypesPayload.SelectedTypes, Is.EqualTo(_defaultViewTypes));
            Assert.That(payload.HorizontalMeasurementTypesPayload.MeasurementTypes, Is.EqualTo(_measurementTypes));
            Assert.That(payload.Series.Count(), Is.EqualTo(2));
            Assert.That(payload.PrimarySeriesTitle, Is.EqualTo("Weight"));
            Assert.That(payload.SecondarySeriesTitle, Is.EqualTo("Body Fat %"));

            _bodyProvider.VerifyAllExpectations();
        }

        [Test]
        public void Test_Index_Post_ReturnsViewWithThreeSeries()
        {
            // Arrange
            _bodyProvider.Expect(bp => bp.SeriesForIndicator(Arg<Account>.Is.Anything, Arg<MeasurementType>.Is.Equal(_weight), Arg<bool>.Is.Equal(false))).Return(_weightSeries);
            _bodyProvider.Expect(bp => bp.SeriesForIndicator(Arg<Account>.Is.Anything, Arg<MeasurementType>.Is.Equal(_bodyFat), Arg<bool>.Is.Equal(false))).Return(_bodyfatSeries);
            _bodyProvider.Expect(bp => bp.SeriesForIndicator(Arg<Account>.Is.Anything, Arg<MeasurementType>.Is.Equal(_chest), Arg<bool>.Is.Equal(false))).Return(_chestSeries);
            FormCollection formCollection = new FormCollection
                                                {
                                                    {_weight.ToFormName(), "true"},
                                                    {_bodyFat.ToFormName(), "true"},
                                                    {_chest.ToFormName(), "true"}
                                                };

            // Act
            ViewResult result = (ViewResult)_bodyController.Index(formCollection);

            // Assert
            var payload = (IndexPayload)result.ViewData.Model;

            Assert.That(payload.HorizontalMeasurementTypesPayload.IncludeButton, Is.True);
            Assert.That(payload.HorizontalMeasurementTypesPayload.SelectedTypes.First(), Is.EqualTo(_weight));
            Assert.That(payload.HorizontalMeasurementTypesPayload.SelectedTypes.Skip(1).First(), Is.EqualTo(_bodyFat));
            Assert.That(payload.HorizontalMeasurementTypesPayload.SelectedTypes.Skip(2).First(), Is.EqualTo(_chest));
            Assert.That(payload.HorizontalMeasurementTypesPayload.SelectedTypes.Count(), Is.EqualTo(3));
            Assert.That(payload.HorizontalMeasurementTypesPayload.MeasurementTypes, Is.EqualTo(_measurementTypes));
            Assert.That(payload.Series.Count(), Is.EqualTo(3));
            Assert.That(payload.PrimarySeriesTitle, Is.EqualTo("Weight"));
            Assert.That(payload.SecondarySeriesTitle, Is.EqualTo("Others"));

            _bodyProvider.VerifyAllExpectations();
        }

        #endregion

        #region Create

        [Test]
        public void Test_Create_Get_ReturnsView()
        {
            // Arrange
            Body consolidatedBody = new Body();
            consolidatedBody.Measurements.Add(new Measurement(){ Value = 85, MeasurementType = _weight});
            consolidatedBody.Measurements.Add(new Measurement() { Value = 15, MeasurementType = _bodyFat });
            _bodyProvider.Stub(bp => bp.ConsolidatedBody(null)).Return(consolidatedBody).IgnoreArguments();

            // Act
            ViewResult result = (ViewResult) _bodyController.Create();

            // Assert
            BodyEditorPayload payload = (BodyEditorPayload) result.ViewData.Model;           
            Assert.That(payload.EditMode, Is.EqualTo(BodyEditorPayload.EditModeEnum.Create));
            Assert.That(payload.MeasurementTypes, Is.EqualTo(_measurementTypes));
            Assert.That(payload.Body, Is.Not.Null);
            Assert.That(payload.PreviousBody, Is.EqualTo(consolidatedBody));
        }

        [Test]
        public void Test_Create_Get_ReturnsViewNoPreviousBody()
        {
            // Arrange
            _bodyProvider.Stub(bp => bp.ConsolidatedBody(null)).Return(null).IgnoreArguments();

            // Act
            ViewResult result = (ViewResult)_bodyController.Create();

            // Assert
            BodyEditorPayload payload = (BodyEditorPayload)result.ViewData.Model;            
            Assert.That(payload.EditMode, Is.EqualTo(BodyEditorPayload.EditModeEnum.Create));
            Assert.That(payload.MeasurementTypes, Is.EqualTo(_measurementTypes));
            Assert.That(payload.Body, Is.Not.Null);
            Assert.That(payload.PreviousBody, Is.Null);
        }

        [Test]
        public void Test_Create_Post_RedirectsToGrid()
        {
            // Arrange
            Body body = new Body();
            _bodyProvider.Stub(bp => bp.CurrentAccount(null)).Return(_mockAccount).IgnoreArguments();
            _bodyProvider.Expect(bp => bp.Add(null)).IgnoreArguments();

            // Act
            RedirectToRouteResult result = (RedirectToRouteResult) _bodyController.Create(body);

            // Assert
            Assert.That(body.Account, Is.EqualTo(_mockAccount));
            Assert.That(result.RouteValues["action"], Is.EqualTo("Grid"));

            _bodyProvider.VerifyAllExpectations();
        }

        [Test]
        public void Test_Create_Post_AddExceptionHandled()
        {
            // Arrange
            Body body = new Body();
            _bodyProvider.Expect(bp => bp.Add(null)).IgnoreArguments().Throw(new Exception());

            // Act
            ViewResult result = (ViewResult) _bodyController.Create(body);

            // Assert
            Assert.That(result.ViewData.ModelState["_FORM"].Errors[0].ErrorMessage, Is.EqualTo("Unexpected error occurred while saving."));
            _bodyProvider.VerifyAllExpectations();
        }

        #endregion

        #region Delete

        [Test]
        public void Test_Delete_Ajax_Delete()
        {
            // Arrange
            Body body = new Body
            {
                Account =  _mockAccount,
                EntryDate = DateTime.Now
            };
            _bodyProvider.Expect(bp => bp.Delete(null, 0)).IgnoreArguments();
            _bodyProvider.Stub(bp => bp.CurrentAccount(null)).IgnoreArguments().Return(_mockAccount);
            _httpContext.IsAjaxRequest = true;

            // Act
            JsonResult result = (JsonResult)_bodyController.DeleteBody(1);

            // Assert
            Assert.That(result.Data, Is.True);
            _bodyProvider.VerifyAllExpectations();
        }

        // Testing the unauthorized exceptions is still important but is now at the provider level
        /*
        [Test]
        public void Test_Delete_Ajax_DeleteFailsWhenUnauthorised()
        {
            // Arrange
            Body body = new Body
            {
                Account = _mockAccount,
                EntryDate = DateTime.Now
            };
            _bodyProvider.Expect(bp => bp.GetBodyById(null, 0)).IgnoreArguments().Return(body);
            _bodyProvider.AssertWasNotCalled(bp => bp.Delete(Arg<Account>.Is.Anything, Arg<Body>.Is.Anything));
            _bodyProvider.Stub(bp => bp.CurrentAccount(null)).IgnoreArguments().Return(new Account());
            _httpContext.IsAjaxRequest = true;

            // Act
            JsonResult result = (JsonResult)_bodyController.DeleteBody(1);

            // Assert
            Assert.AreEqual(false, result.Data);
            _bodyProvider.VerifyAllExpectations();
        }*/

        [Test]
        public void Test_Delete_Get_ReturnsEmptyResultNoDeleteOccurs()
        {
            // Arrange
            _bodyProvider.AssertWasNotCalled(bp => bp.Delete(Arg<Account>.Is.Anything, Arg<int>.Is.Anything));
            _httpContext.IsAjaxRequest = false;

            // Act
            EmptyResult result = (EmptyResult)_bodyController.DeleteBody(1);

            // Assert
            _bodyProvider.VerifyAllExpectations();
        }

        #endregion

        #region Export to XML

        [Test]
        public void Test_ExportToXml_Get_WritesXml()
        {
            // Arrange
            _bodyProvider.Expect(bp => bp.ToXml(null, null)).IgnoreArguments();

            // Act
            _bodyController.ExportToXml();

            // Assert
            _bodyProvider.VerifyAllExpectations();
        }

        #endregion

        #region Edit

        [Test]
        public void Test_Edit_Get_ReturnsView()
        {
            // Arrange
            DateTime now = DateTime.Now;
            Body body = new Body
            {
                BodyId = 1,
                Account = _mockAccount,
                EntryDate = now
            };
            body.Measurements.Add(new Measurement() { Value = 85, MeasurementType = _weight });
            body.Measurements.Add(new Measurement() { Value = 15, MeasurementType = _bodyFat });
            _bodyProvider.Stub(bp => bp.GetBodyById(null, 0)).Return(body).IgnoreArguments();
            _bodyProvider.Stub(bp => bp.CurrentAccount(null)).Return(_mockAccount).IgnoreArguments();

            // Act
            ViewResult result = (ViewResult) _bodyController.Edit(1);

            // Assert
            BodyEditorPayload payload = (BodyEditorPayload)result.ViewData.Model;
            Assert.That(payload.EditMode, Is.EqualTo(BodyEditorPayload.EditModeEnum.Edit));
            Assert.That(payload.MeasurementTypes, Is.EqualTo(_measurementTypes));
            Assert.That(payload.Body, Is.Not.Null);
            Assert.That(payload.PreviousBody, Is.Null);
        }

        // This unit test now needs to take place at the provider level. Leaving here until done.
        /*
        [ExpectedException(typeof(AuthorizationException))]
        [Test]
        public void Test_Edit_Get_UnauthorisedAccessThrowsException()
        {
            // Arrange
            DateTime now = DateTime.Now;
            Body body = new Body
            {
                BodyId = 1,
                Account = _mockAccount,
                EntryDate = now,
            };
            _bodyProvider.Stub(bp => bp.GetBodyById(null, 0)).Return(body).IgnoreArguments();
            _bodyProvider.Stub(bp => bp.CurrentAccount(null)).Return(new Account()).IgnoreArguments();

            // Act
            _bodyController.Edit(1);

            // Assert
            // Exception caught via attribute on test
        }*/

        [Test]
        public void Test_Edit_Post_RedirectsToGrid()
        {
            // Arrange
            DateTime now = DateTime.Now;
            Body body = new Body
            {
                Account = _mockAccount,
                EntryDate = now
            };
            _bodyProvider.Expect(bp => bp.Update(null, null)).IgnoreArguments();
            body.Measurements.Add(new Measurement() { Value = 85, MeasurementType = _weight });
            body.Measurements.Add(new Measurement() { Value = 15, MeasurementType = _bodyFat });
            _bodyProvider.Stub(bp => bp.GetBodyById(null, 0)).Return(body).IgnoreArguments();
            _bodyProvider.Stub(bp => bp.CurrentAccount(null)).Return(_mockAccount).IgnoreArguments();

            // Act
            RedirectToRouteResult result = (RedirectToRouteResult) _bodyController.Edit(1, body);

            // Assert
            Assert.That(result.RouteValues["action"], Is.EqualTo("Grid"));
            _bodyProvider.VerifyAllExpectations();
        }

        /// <summary>
        /// This is a poor test at the moment as we are testing that something we throw from a mock is the output.
        /// Its really a placeholder as unauthorized access is about to get refactored.
        /// </summary>
        [Test]
        public void Test_Edit_Post_UnauthorizedAccessThrowsException()
        {
            // Arrange
            DateTime now = DateTime.Now;
            Body body = new Body
            {
                Account = _mockAccount,
                EntryDate = now,
            };
            _bodyProvider.Stub(bp => bp.Update(null, null)).IgnoreArguments().Throw(new AuthorizationException("Bad access"));

            // Act
            RedirectToRouteResult redirectResult = (RedirectToRouteResult)_bodyController.Edit(1, body);

            // Assert            
            Assert.That(redirectResult.RouteValues["controller"], Is.EqualTo("Account"));
            Assert.That(redirectResult.RouteValues["action"], Is.EqualTo("Unauthorized"));
        }

        [Test]
        public void Test_Edit_Post_ValidationErrorReturnsView()
        {
            // Arrange
            DateTime now = DateTime.Now;
            Body body = new Body
            {
                BodyId = 1,
                Account = _mockAccount,
                EntryDate = now,
            };

            _bodyProvider.Expect(bp => bp.Update(null, null)).IgnoreArguments().Throw(
                new ValidationException(new NameValueCollection { { "_FORM", "Error" } }));

            // Act
            ViewResult result = (ViewResult)_bodyController.Edit(1, body);

            // Assert
            BodyEditorPayload payload = (BodyEditorPayload)result.ViewData.Model;
            Assert.That(payload.EditMode, Is.EqualTo(BodyEditorPayload.EditModeEnum.Edit));
            Assert.That(payload.MeasurementTypes, Is.EqualTo(_measurementTypes));
            Assert.That(payload.Body, Is.Not.Null);
            Assert.That(payload.PreviousBody, Is.Null);
        }

        #endregion

        #region Grid

        [Test]
        public void Test_Grid_Get_ReturnsView()
        {
            // Act
            ViewResult result = (ViewResult) _bodyController.Grid();

            // Assert
            // Nothing to assert
        }

        [Test]
        public void Test_GridData_Get_ReturnsJson()
        {
            // Arrange
            Body body = new Body
            {
                BodyId = 1,
                Account = _mockAccount,
                EntryDate = DateTime.Now
            };

            _bodyProvider.Stub(bp => bp.CurrentAccount(null)).Return(_mockAccount);
            _bodyProvider.Stub(bp => bp.TotalBodiesForAccount(null)).IgnoreArguments().Return(1);
            _bodyProvider.Stub(bp => bp.BodiesForAccount(null, 0, 10)).IgnoreArguments().Return(new[] {body});

            // Act
            JsonResult result = (JsonResult) _bodyController.GridData("", "", 0, 10);

            // Assert
            // Need to consider using a strong type instead of the anonymous type as the anonymous type
            // makes it tricky to unit test the actual values in the Json data. Then we can do more testing
            // for paging etc.
            Assert.That(result.Data, Is.Not.Null);
        }

        #endregion
    }
}
